Limpiar filtro
Pregunta
Yone Moreno · 30 mar, 2022
Buenos días,
Primero, ante todo, muchas gracias de antemano por leernos y responder
Además, agradecer cualquier apoyo, porque es un alivio, apoyo, aporte, auxilio contar con personas con más entendimiento, conocimiento y práctica.
Existe la siguiente necesidad:
Se dispone de 2 circuitos:
1º Circuito DICOM de "Studio" ( Servicio clásico )
Servicio: clase: DICOM.BS.QueryService
Proceso: clase: DICOM.BP.QueryProcess
Operacion: clase: EnsLib.DICOM.Operation.TCP
Probamos desde la "Salida" del "Studio" mediante:
do ##class(DICOM.BS.QueryService).TestFind("102030")
Donde "102030" es el PatientID del usuario
Lo interesante aquí es la traza:
A nivel visual:
Lo importante, enviamos en la petición a Destino en el DICOM.Document:
Recibimos bastante contenido:
Sin embargo, mediante el otro circuito hay discrepancias:
2º Circuito DICOM de TCP( Se prueba por línea de comandos )
Servicio: clase: EnsLib.DICOM.Service.TCP
Proceso: clase: DICOM.BP.QueryProcess
Operacion: clase: EnsLib.DICOM.Operation.TCP
Para probar ejecutamos:
./findscu -b VNAPRE -c ESBPRE@10.XWZ.4.ABC:19XYZ -m PatientID="102030"
La traza visual:
Siendo lo enviado, a sistema destino, mediante DICOM.Document:
Aquí viene la cuestión:
¿ por qué sale muy poca informacion en la respuesta del Destino ?
¿ por qué sale muy poca informacion en la respuesta del Destino ?
En particular la traza a nivel de línea de comandos:
$ ./findscu -b VNAPRE -c ESBPRE@10.[IP_Destino]:19586 -m PatientID="102030"
10:30:38.820 INFO - Initiate connection from 0.0.0.0/0.0.0.0:0 to 10.[IP_Destino]:19586
10:30:38.867 INFO - Established connection Socket[addr=/10.[IP_Destino],port=19586,localport=52515]
10:30:38.888 DEBUG - /10.[IP_Origen]:52515->/10.[IP_Destino]:19586(1): enter state: Sta4 - Awaiting transport connection opening to complete
10:30:38.890 INFO - VNAPRE->ESBPRE(1) << A-ASSOCIATE-RQ
10:30:38.890 DEBUG - A-ASSOCIATE-RQ[
calledAET: ESBPRE
callingAET: VNAPRE
applicationContext: 1.2.840.10008.3.1.1.1 - DICOM Application Context Name
implClassUID: 1.2.40.0.13.1.3
implVersionName: null
maxPDULength: 16378
maxOpsInvoked/maxOpsPerformed: 0/0
PresentationContext[id: 1
as: 1.2.840.10008.5.1.4.1.2.2.1 - Study Root Query/Retrieve Information Model - FIND
ts: 1.2.840.10008.1.2 - Implicit VR Little Endian
ts: 1.2.840.10008.1.2.1 - Explicit VR Little Endian
ts: 1.2.840.10008.1.2.2 - Explicit VR Big Endian (Retired)
]
]
10:30:38.923 DEBUG - VNAPRE->ESBPRE(1): enter state: Sta5 - Awaiting A-ASSOCIATE-AC or A-ASSOCIATE-RJ PDU
10:30:38.936 INFO - VNAPRE->ESBPRE(1) >> A-ASSOCIATE-AC
10:30:38.937 DEBUG - A-ASSOCIATE-AC[
calledAET: ESBPRE
callingAET: VNAPRE
applicationContext: 1.2.840.10008.3.1.1.1 - DICOM Application Context Name
implClassUID: 1.2.840.114475.1
implVersionName: ENSDICOM
maxPDULength: 16378
maxOpsInvoked/maxOpsPerformed: 1/1
PresentationContext[id: 1
result: 0 - acceptance
ts: 1.2.840.10008.1.2 - Implicit VR Little Endian
]
]
10:30:38.938 DEBUG - VNAPRE->ESBPRE(1): enter state: Sta6 - Association established and ready for data transfer
10:30:38.946 INFO - VNAPRE->ESBPRE(1) << 1:C-FIND-RQ[pcid=1, prior=0
cuid=1.2.840.10008.5.1.4.1.2.2.1 - Study Root Query/Retrieve Information Model - FIND
tsuid=1.2.840.10008.1.2 - Implicit VR Little Endian]
10:30:38.947 DEBUG - VNAPRE->ESBPRE(1) << 1:C-FIND-RQ Command:
(0000,0002) UI [1.2.840.10008.5.1.4.1.2.2.1] AffectedSOPClassUID
(0000,0100) US [32] CommandField
(0000,0110) US [1] MessageID
(0000,0700) US [0] Priority
(0000,0800) US [0] CommandDataSetType
10:30:39.006 DEBUG - VNAPRE->ESBPRE(1) << 1:C-FIND-RQ Dataset:
(0008,0052) CS [STUDY] QueryRetrieveLevel
(0010,0020) LO [102030] PatientID
10:30:39.170 INFO - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP[pcid=1, status=ff00H
cuid=1.2.840.10008.5.1.4.1.2.2.1 - Study Root Query/Retrieve Information Model - FIND
tsuid=1.2.840.10008.1.2 - Implicit VR Little Endian]
10:30:39.171 DEBUG - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP Command:
(0000,0002) UI [1.2.840.10008.5.1.4.1.2.2.1] AffectedSOPClassUID
(0000,0100) US [32800] CommandField
(0000,0120) US [1] MessageIDBeingRespondedTo
(0000,0800) US [65278] CommandDataSetType
(0000,0900) US [65280] Status
10:30:39.173 DEBUG - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP Dataset:
(0008,0052) CS [STUDY] QueryRetrieveLevel
(0008,0054) AE [VNAPRE] RetrieveAETitle
(0010,0020) LO [102030] PatientID
10:30:39.175 INFO - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP[pcid=1, status=ff00H
cuid=1.2.840.10008.5.1.4.1.2.2.1 - Study Root Query/Retrieve Information Model - FIND
tsuid=1.2.840.10008.1.2 - Implicit VR Little Endian]
10:30:39.175 DEBUG - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP Command:
(0000,0002) UI [1.2.840.10008.5.1.4.1.2.2.1] AffectedSOPClassUID
(0000,0100) US [32800] CommandField
(0000,0120) US [1] MessageIDBeingRespondedTo
(0000,0800) US [65278] CommandDataSetType
(0000,0900) US [65280] Status
10:30:39.176 DEBUG - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP Dataset:
(0008,0052) CS [STUDY] QueryRetrieveLevel
(0008,0054) AE [VNAPRE] RetrieveAETitle
(0010,0020) LO [102030] PatientID
10:30:39.176 INFO - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP[pcid=1, status=ff00H
cuid=1.2.840.10008.5.1.4.1.2.2.1 - Study Root Query/Retrieve Information Model - FIND
tsuid=1.2.840.10008.1.2 - Implicit VR Little Endian]
10:30:39.176 DEBUG - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP Command:
(0000,0002) UI [1.2.840.10008.5.1.4.1.2.2.1] AffectedSOPClassUID
(0000,0100) US [32800] CommandField
(0000,0120) US [1] MessageIDBeingRespondedTo
(0000,0800) US [65278] CommandDataSetType
(0000,0900) US [65280] Status
10:30:39.177 DEBUG - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP Dataset:
(0008,0052) CS [STUDY] QueryRetrieveLevel
(0008,0054) AE [VNAPRE] RetrieveAETitle
(0010,0020) LO [102030] PatientID
Observamos que existen 3 respuestas:
10:30:39.170 INFO - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP
10:30:39.175 INFO - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP
10:30:39.176 INFO - VNAPRE->ESBPRE(1) >> 1:C-FIND-RSP
Sin embargo ¿ por qué sale muy poca informacion en la respuesta del Destino ?
En concreto, en el circuito 1º obtenemos:
SpecificCharacterSet, StudyDate, StudyDescription, PatientName, StudyInstanceUID
Mientras que en el TCP de DICOM, en el 2º circuito:
Unicamente: QueryRetrieveLevel, RetrieveAETitle, PatientID
¿Ustedes nos podrían, por favor, indicar documentación, ejemplos de código, trazas visuales, o referencias de cualquier tipo que nos aporten para depurar?
Muchas gracias de antemano por su tiempo, al leer y responder
Un saludo Hola Yone,
Imagino que habrá diferencias entre el C-FIND que se envía en el primer caso y el segundo.
En el primer caso, el que retorna más resultados, entiendo que es el caso del ejemplo. Revisa cómo se está generando exactamente ese C-FIND:
https://github.com/intersystems-ib/iris-dicom-sample/blob/7bc3c00dfa1bbbe9b0a711df2f344a15dee09be1/iris/src/DICOM/%20Msg/QueryReq.cls#L27
En principio, se está haciendo un PatientRootQuery (tAffectedSOPClassUID).
No sé si puede servirte para algo, pero en el herramienta findscu creo que hay una opción para especificar el Information Model a utilizar:
https://github.com/dcm4che/dcm4che/blob/master/dcm4che-tool/dcm4che-tool-findscu/README.md
-M <name> specifies Information Model.
Supported names: PatientRoot,
StudyRoot, PatientStudyOnly,
MWL, UPSPull, UPSWatch,
UPSQuery, HangingProtocol or
ColorPalette. If no Information
Model is specified, StudyRoot
will be used.
Es de agradecer tu respuesta Alberto
Son un apoyo tus explicaciones, ejemplos y enlaces
Los revisamos y te responderíamos Es de agradecer, Alberto, tu respuesta, y sobre todo las explicaciones y enlaces
A continuación te detallamos la situación, Alberto, para que ustedes nos indiquen, ordenen, recomienden, sugieran vías o formas de continuar:
A continuación, se muestran las trazas del WireShark
Primero observamos la traza Simulada desde Línea de Comandos mediante:
./findscu -b VNAPRE -c ESBPRE@10.136.4.142:19586 -m PatientID="102030" -M StudyRoot
Segundo se muestra la traza Real desde Sistema Origen hacia Sistema Destino:
La principal diferencia radica en la petición, la cual está en 1 único paquete en la Simulada, el cual está titulado como:
1072 … P-DATA, C-FIND-RQ ID=1, C-FIND-RQ-DATA
Sin embargo, en la real, la petición consta de 2 paquetes:
1904 … P-DATA, C-FIND-RQ ID=1
1906 … P-DATA, C-FIND-RQ-DATA
Ahondando en el detalle, comparamos la primera de las peticiones titulada como: “P-DATA, C-FIND-RQ ID=1”
En la Simulada se observa lo siguiente:
Donde cabe destacar estos 2 contenidos del conjunto de comandos (Command, Last Fragment):
…
(0000,0700) 2 Priority 0
(0000,0800) 2 Command Data Set Type 0
Los cuales en la Real son distintos:
…
(0000,0700) 2 Priority 2
(0000,0800) 2 Command Data Set Type 1
Además, también es importante recalcar que en la segunda petición reseñada como “P-DATA, C-FIND-RQ-DATA” existen diferencias:
En la Real es más completa:
PDV, C-FIND-RQ-DATA
PDV Length: 130
Context: 0x01 (Implicit VR Little Endian: Default Transfer Syntax for DICOM, Study Root Query/Retrieve Information Model - FIND)
Flags: 0x02 (Data, Last Fragment)
(0008,0005) 0 Specific Character Set <Empty>
(0008,0020) 0 Study Date <Empty>
(0008,0030) 0 Study Time <Empty>
(0008,0050) 0 Accession Number <Empty>
(0008,0052) 6 Query/Retrieve Level STUDY
(0008,0061) 0 Modalities in Study <Empty>
(0008,0090) 0 Referring Physician's Name <Empty>
(0008,1010) 0 Station Name <Empty>
(0008,1030) 0 Study Description <Empty>
(0010,0010) 0 Patient's Name <Empty>
(0010,0020) 2 Patient ID 9
(0010,0030) 0 Patient's Birth Date <Empty>
(0010,0040) 0 Patient's Sex <Empty>
(0020,000d) 0 Study Instance UID <Empty>
(0020,0010) 0 Study ID <Empty>
Sin embargo, en la Simulada únicamente existen 2 datos:
PDV, C-FIND-RQ-DATA
PDV Length: 30
Context: 0x01 (Implicit VR Little Endian: Default Transfer Syntax for DICOM, Study Root Query/Retrieve Information Model - FIND)
Flags: 0x02 (Data, Last Fragment)
(0008,0052) 6 Query/Retrieve Level STUDY
(0010,0020) 6 Patient ID 102030
Por otro lado, lo que nos genera mayor inquietud, nos pone en vilo, y es extraño, resulta ser las discrepacias entre los resultados de los estudios médicos obtenidos mediante servicio ‘interno’ y mediante ‘simulación por herramienta externa’ hacia el Servicio TCP de DICOM
En concreto cuando ejecutamos desde la “Salida” del “Studio” de ESBPRE lo siguiente:
do ##class(DICOM.BS.QueryService).TestFind("102030")
Se envía la petición:
Y para cada uno de los 3 estudios médicos respondidos por el Sistema Destino, obtenemos datos completos:
Mientras que cuando simulamos ser Sistema Origen, gracias a la herramienta ‘findscu’ de Línea de Comandos, ejecutamos:
./findscu -b VNAPRE -c ESBPRE@10.136.4.142:19586 -m PatientID="102030" -M StudyRoot
Generándose la petición siguiente en la traza:
Si prestamos atención a la imagen anterior, se visualiza que únicamente se remiten 2 filas en el “DataSet”, las cuales corresponden a: ‘QueryRetrieveLevel’ y ‘PatientID’
Siendo la respuesta por parte del Sistema Origen con 3 estudios médicos, donde únicamente se incluyen 3 filas en el DataSet:
Las cuales corresponden al: ‘QueryRetrieveLevel’, ‘RetrieveAETitle’ y ‘PatientID’
De esta forma observamos grandes diferencias entre la respuesta generada por el Sistema Destino, cuando enviamos desde nuestro servicio interno, frente a cuando remitimos la consulta simulando ser sistema origen por TCP de DICOM
¿ustedes cómo recomiendan continuar?
Muchas gracias, Alberto, por leer, responder, y tomar su tiempo para atendernos
Gracias a ustedes por indicarnos, ordenarnos, recomendarnos, sugerirnos, técnicas, documentación, ejemplos; mediante los cuales investigar, depurar, indagar esta cuestión
Un saludo
Artículo
Kurro Lopez · 25 nov, 2019
Principiantes- ver Parte 1.
3. Variantes de estructuras cuando se usan globals
Una estructura, como un árbol ordenado, tiene varios casos especiales. Echemos un vistazo a aquellos que tienen un valor práctico para trabajar con globals.
3.1 Caso especial 1. Un nodo sin ramas
Los globals pueden usarse no solo como una matriz, sino como variables regulares. Por ejemplo, para crear un contador:
Set ^counter = 0 ; setting counter
Set id=$Increment(^counter) ; atomic incrementation
Al mismo tiempo, un global puede tener ramas adicionales además de su valor. Uno no excluye al otro.
3.2 Caso especial 2. Un nodo y múltiples ramas
De hecho, es una base clásica de valor clave. Y si guardamos tuplas de valores en lugar de valores, obtendremos una tabla regular con una clave primaria.
Para implementar una tabla basada en globals, tendremos que formar cadenas de valores de columna, luego guardarlas en un global por la clave primaria. Para poder dividir la cadena en columnas durante la lectura, podemos usar lo siguiente:
Caracteres delimitadores
Set ^t(id1) = "col11/col21/col31"
Set ^t(id2) = "col12/col22/col32"
Un esquema fijo, por el cual cada campo ocupa un número particular de bytes. Así es como generalmente se hace en bases de datos relacionales.
Una función especial $LB (introducida en Caché) que compone una cadena de valores.
Set ^t(id1) = $LB("col11", "col21", "col31")
Set ^t(id2) = $LB("col12", "col22", "col32")
Lo interesante es que no es difícil hacer algo similar a las claves foráneas en bases de datos relacionales que usan globals. Llamemos a tales estructuras index globals. Un índice global es un árbol suplementario para la búsqueda rápida por campos que no son una parte integral de la clave primaria del global principal. Necesita escribir código adicional para llenarlo y usarlo.
Creemos un índice global basado en la primera columna.
Set ^i("col11", id1) = 1
Set ^i("col12", id2) = 1
Para buscar rápidamente por la primera columna, deberá buscar en ^i global y encontrar las claves principales (id) correspondientes al valor necesario en la primera columna.
Al insertar un valor, podemos crear valores e índices globals para los campos necesarios. Para mayor fiabilidad, envuélvala en una transacción.
TSTART
Set ^t(id1) = $LB("col11", "col21", "col31")
Set ^i("col11", id1) = 1
TCOMMIT
Más información sobre creando tablas en M usando globals y emulación de claves secundarias.
Estas tablas funcionarán tan rápido como en las bases de datos tradicionales (o incluso más rápido) si las funciones de inserción/actualización/eliminación se escriben en COS/M y se compilan.
Verifiqué esta declaración aplicando una gran cantidad de operaciones INSERT y SELECT a una sola tabla de dos columnas, también usando el comando TSTART y TCOMMIT (transacciones).
No he probado escenarios más complejos con acceso concurrente y transacciones paralelas.
Sin usar transacciones, la velocidad de inserción para un millón de valores fue de 778.361 inserciones/seg.
Para 300 millones de valores, la velocidad fue de 422.141 inserciones/segundo.
Cuando se utilizaron las transacciones, la velocidad alcanzó 572.082 inserciones/segundo para 50 millones de valores. Todas las operaciones se ejecutaron desde el código M compilado. Usé discos duros normales, no SSD. RAID5 con reescritura. Todo se ejecuta en una CPU Phenom II 1100T.
Para realizar la misma prueba para una base de datos SQL, necesitaríamos escribir un procedimiento almacenado que haga inserciones en un bucle. Al probar MySQL 5.5 (almacenamiento InnoDB) utilizando el mismo método, nunca obtuve más de 11K inserciones por segundo.
Correcto, la implementación de tablas con globals es más compleja que hacer lo mismo en bases de datos relacionales. Es por eso que los DB industriales basados en globales tienen acceso SQL para un trabajo simplificado con datos tabulares.
En general, si el esquema de datos no va a cambiar con frecuencia, la velocidad de inserción no es crítica y la base de datos completa se puede representar fácilmente con tablas normalizadas, es más fácil trabajar con SQL, ya que proporciona un mayor nivel de abstracción.
En este caso, quería mostrar que los globals pueden usarse como constructores para crear otras bases de datos. Al igual que el lenguaje ensamblador que se puede usar para crear otros idiomas. Y aquí hay algunos ejemplos de uso de globals para crear contrapartes de valores-clave, listas, conjuntos, tablas, bases de datos orientadas a documentos.
Si necesita crear una base de datos no estándar con un esfuerzo mínimo, debe considerar el uso de globals.
3.3 Caso especial 3. Un árbol de dos niveles con cada nodo de segundo nivel que tiene un número fijo de ramas
Probablemente lo hayas adivinado: es una implementación alternativa de tablas usando globales. Comparémoslo con el anterior.
Tablas en un árbol de dos niveles vs. Tablas en un árbol de un nivel
Contras
Pros
Inserciones más lentas, ya que el número de nodos debe establecerse igual al número de columnas
Mayor consumo de espacio en el disco duro, ya que los índices globals (como los índices de matriz) con nombres de columna ocupan espacio en el disco duro y se duplican para cada fila
Acceso más rápido a los valores de columnas particulares, ya que no necesita analizar la cadena. Según mis pruebas, es un 11,5% más rápido para 2 columnas e incluso más rápido para más columnas.
Más fácil cambiar el esquema de datos
Código más fácil de leer
Conclusión: No hay nada que destacar. Dado que el rendimiento es una de las ventajas clave de los globals, prácticamente no tiene sentido utilizar este enfoque, ya que es poco probable que funcione más rápido que las tablas normales en bases de datos relacionales.
3.4 Caso general. Árboles y llaves ordenadas
Cualquier estructura de datos que se pueda representar como un árbol se ajusta a los globals de una manera perfecta.
3.4.1 Objetos con subobjetos
Esta es el área donde se usan tradicionalmente los globals. Existen numerosas enfermedades, medicamentos, síntomas y métodos de tratamiento en el área médica. Es irracional crear una tabla con un millón de campos para cada paciente, especialmente porque el 99% de ellos estará en blanco.
Imagine una base de datos SQL compuesta de las siguientes tablas: "Paciente" ~ 100.000 campos, "Medicación" 100.000 campos, "Terapia" 100.000 campos, "Complicaciones" 100.000 campos y así sucesivamente. Como alternativa, puede crear una base de datos con miles de tablas, cada una para un tipo de paciente en particular (¡y también pueden superponerse!), tratamiento, medicamentos y miles de tablas para las relaciones entre estas tablas.
Los globals se ajustan a la atención médica como un guante, ya que hacen posible que cada paciente tenga un registro completo de casos, una lista de terapias, medicamentos administrados y sus efectos, todo en forma de árbol, sin desperdiciar demasiado espacio en el disco en columnas vacías, como sería el caso con las bases de datos relacionales.
Globals funciona bien para bases de datos con detalles personales, cuando la tarea es acumular y sistematizar el máximo de varios datos personales sobre un cliente. Esto es especialmente importante para la salud, la banca, el marketing, el archivo y otras áreas.
No hace falta decir que SQL también le permite emular un árbol usando solo varias tablas (EAV, 1,2,3,4,5,6, 7,8), pero es mucho más complejo y funciona más lento. En esencia, tendríamos que escribir un global basado en tablas y ocultar todas las rutinas relacionadas con tablas bajo una capa de abstracción. No es correcto emular una tecnología de nivel inferior (globales) con la ayuda de una de nivel superior (SQL). Es simplemente injustificado.
No es un secreto que cambiar un esquema de datos en tablas gigantes (ALTER TABLE) puede llevar una cantidad considerable de tiempo. MySQL, por ejemplo, realiza la operación ALTER TABLE ADD|DROP COLUMN copiando todos los datos de la tabla anterior a la nueva (lo probé en MyISAM e InnoDB). Que puede colgar una base de datos de producción con miles de millones de registros durante días, si no semanas.
Si estamos usando globals, cambiar la estructura de datos no tiene ningún coste para nosotros. Podemos agregar cualquier propiedad nueva a cualquier objeto en cualquier nivel de la jerarquía en cualquier momento dado. Los cambios que requieren el cambio de nombre de las ramas se pueden aplicar en modo de fondo con la base de datos en funcionamiento.
Por lo tanto, cuando se trata de almacenar objetos con una gran cantidad de propiedades opcionales, los globals funcionan perfectamente bien.
Permítame recordarle que el acceso a cualquiera de las propiedades es instantáneo, ya que en un global, todas las rutas son un árbol B.
En el caso general, las bases de datos basadas en globals son un tipo de bases de datos orientadas a documentos que admiten el almacenamiento de información jerárquica. Por lo tanto, las bases de datos orientadas a documentos pueden competir eficientemente con los globals en el campo del almacenamiento de tarjetas médicas.
Pero todavía no lo es.
Tomemos MongoDB, por ejemplo. En este campo, pierde frente a los globales por las siguientes razones:
Tamaño del documento. La unidad de almacenamiento es un texto en formato JSON (BSON, para ser exactos) con un tamaño máximo de alrededor de 16 MB. La limitación se introdujo a propósito para asegurarse de que la base de datos JSON no se vuelva demasiado lenta durante el análisis, cuando se guarda un gran documento JSON y se abordan valores de campo particulares. Se supone que este documento tiene información completa sobre un paciente. Todos sabemos cuán gruesas pueden ser las tarjetas de pacientes. Si el tamaño máximo de la tarjeta tiene un límite de 16 MB, filtra inmediatamente a los pacientes cuyas tarjetas contienen imágenes de resonancia magnética, rayos X y otros materiales. Una sola rama de un global puede tener gigabytes y petabytes de terabytes de datos. De alguna manera lo dice todo, pero déjame contarte más.
El tiempo requerido para crear/cambiar/eliminar nuevas propiedades de la tarjeta del paciente. Dicha base de datos necesitaría copiar toda la tarjeta en la memoria (¡muchos datos!), Analizar los datos de BSON, agregar/cambiar/eliminar el nuevo nodo, actualizar índices, empaquetarlo todo nuevamente en BSON y guardarlo en el disco. Un global solo necesitaría abordar la propiedad necesaria y realizar la operación necesaria.
Velocidad de acceso a propiedades particulares. Si el documento tiene muchas propiedades y una estructura de varios niveles, el acceso a propiedades particulares será más rápido porque cada ruta en el global es un árbol B. En BSON, deberá analizar linealmente el documento para encontrar la propiedad necesaria.
3.3.2 Matrices asociativas
Las matrices asociativas (incluso con matrices anidadas) funcionan perfectamente con globals. Por ejemplo, esta matriz PHP se verá como la primera ilustración en 3.3.1.
$a = array(
"name" => "Vince Medvedev",
"city" => "Moscow",
"threatments" => array(
"surgeries" => array("apedicectomy", "biopsy"),
"radiation" => array("gamma", "x-rays"),
"physiotherapy" => array("knee", "shoulder")
)
);
3.3.3 Documentos jerárquicos: XML, JSON
También se puede almacenar fácilmente en globals y descomponerse de diferentes maneras.
XML
El método más fácil de descomponer XML en globals es almacenar atributos de etiqueta en nodos. Y si necesita acceso rápido a los atributos de etiqueta, podemos colocarlos en ramas separadas.
<note id=5>
<to>Alex</to>
<from>Sveta</from>
<heading>Reminder</heading>
<body>Call me tomorrow!</body>
</note>
En COS, el código se verá así:
Set ^xml("note")="id=5"
Set ^xml("note","to")="Alex"
Set ^xml("note","from")="Sveta"
Set ^xml("note","heading")="Reminder"
Set ^xml("note","body")="Call me tomorrow!"
Nota: Para XML, JSON y matrices asociativas, puede encontrar una serie de métodos para mostrarlos en globales. En este caso particular, no reflejamos el orden de las etiquetas anidadas en la etiqueta "note". En el ^xml global, las etiquetas anidadas se mostrarán en orden alfabético. Para una visualización precisa del orden, puede usar el siguiente modelo, por ejemplo:
JSON.
El contenido de este documento JSON se muestra en la primera ilustración en la Sección 3.3.1:
var document = {
"name": "Vince Medvedev",
"city": "Moscow",
"threatments": {
"surgeries": ["apedicectomy", "biopsy"],
"radiation": ["gamma", "x-rays"],
"physiotherapy": ["knee", "shoulder"]
},
};
3.3.4 Estructuras idénticas unidas por relaciones jerárquicas.
Ejemplos: estructura de oficinas de ventas, puestos de personas en una estructura MLM.
Base de datos de inicio. Puede usar una evaluación de fuerza de movimiento como el valor del índice de nodo global. En este caso, deberá seleccionar una rama con el mayor peso para determinar el mejor movimiento. En el global, todas las ramas en cada nivel se ordenarán por la fuerza del movimiento.
La estructura de las oficinas de ventas, las personas en una empresa de MLM. Los nodos pueden almacenar algunos valores de almacenamiento en caché que reflejan las características de todo el subárbol. Por ejemplo, las ventas de este subárbol en particular. Podemos obtener información exacta sobre los logros de cualquier sucursal en cualquier momento.
4. Situaciones en las que merece la pena usar globals
La primera columna contiene una lista de casos en los que el uso de globals le dará una ventaja considerable en términos de rendimiento; y la segunda, una lista de situaciones en las que simplificarán el desarrollo o el modelo de datos.
Velocidad
Conveniencia de procesamiento/presentación de datos
Inserción [con clasificación automática en cada nivel], [indexación por la clave primaria]
Eliminación de subárbol
Objetos con muchas propiedades anidadas a las que necesita acceso individual
Una estructura jerárquica con la posibilidad de atravesar ramas secundarias a partir de cualquier rama, incluso una no existente
Transversal de árboles en profundidad
Objetos/instancias con una gran cantidad de propiedades/instancias no requeridas [y/o anidadas]
Datos sin esquema: a menudo se pueden agregar nuevas propiedades y eliminar las antiguas
Necesita crear una base de datos no estándar
Bases de datos de ruta y árboles de soluciones. Cuando los caminos se pueden representar convenientemente como un árbol
Eliminación de estructuras jerárquicas sin usar recursividad
Descargo de responsabilidad: Este artículo y mis comentarios reflejan solo mi opinión y no tienen nada que ver con la posición oficial de InterSystems Corporation. Buen artículo, muy esclarecedor, bueno para entender un poco sobre cómo funciona y cómo usar global.
Artículo
Pierre-Yves Duquesnoy · 3 mayo, 2022
Encontrar errores en tu código o examinar un comportamiento inesperado es el principal objetivo de la depuración.
Trataré de actualizar las herramientas tradicionales aparte de las ayudas que tienen Studio, VScode, Serenji... Las herramientas básicas que han estado ahí antes de que tu EDI preferido lo utilizara en segundo plano. Observamos 3 situaciones habituales:
Depuración desde la línea de comandos en primer plano desde un terminal o una sesión similar.
Si es posible, el antiguo comando BREAK (también con una postcondición) Te permite detenerte y examinar la situación cuando lo necesites. Es la opción más antigua, que se remonta al periodo anterior a ObjectScript.
Más nuevo, pero similar, el comando ZBREAK, introduce puntos de control, además de simples Puntos de interrupción y un sofisticado conjunto de condiciones.
Aquí puedes consultar más información sobre la Depuración desde la línea de comandos
Depuración en segundo plano
Todo lo que se describe aquí es, por supuesto, igualmente útil y está disponible para la depuración desde la línea de comandos
Primero, tienes la opción de escribir información adicional en un Global (temporal).
Esto es útil si ya tienes una sospecha bastante clara de lo que hay que revisar
Después, puedes volcar sus variables reales también a SPOOL o utilizar una salida redirigida
o llamar a LOG^%ETN() para volcar toda la partición en ^ERRORS Global
y examinarlo con SMP/System Operation/Application error log
Como forma de mover el código de background al primer plano en la línea de comandos, puedes utilizar la depuración desde el intérprete de comandos o Shell.
Esto se aplica normalmente a SQL y CSP/ZEN
Depuración desde el intérprete de comandos
Empiezo con una página CSP bastante simple como un ejemplo que contiene este código:
. . .
</head>
<script language="Cache" method="init" arguments="file,.tName" returntype="%Integer">
BREAK ;; <<<< for debugging
if 'file {
set tName="*** no file ***",index=""
} elseif $D(^ImportFile(file,-1)) {
set tName=$O(^ImportFile(file,-1,"Class",""),1,index)
} Else {
set tName=$g(^ImportFile(file)),index=0
}
quit index
</script>
<script language="Cache" method=
Ahora ejecuto esto desde mi CSPshell y puedo ver y acceder a todas las variables y objetos
IRISAPP>
IRISAPP>do $system.CSP.Shell()
CSP Shell
CSP:IRISAPP>>> GET /csp/irisapp/Details.csp
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: CSPSESSIONID-UP-csp-irisapp-=15340 15340; path=/csp/irisapp/; httpOnly; sameSite=strict;
Cache-Control: no-cache
Date: Tue, 23 Nov 2021 19:03:29 GMT
Expires: Thu, 29 Oct 1998 17:04:19 GMT
Pragma: no-cache
<html>
<head>
<!-- Put your page Title here -->
<title> Details </title>
<script language="javascript">
window.onmessage = function(event){
};
</script>
<!--
function postDebug(item, index) {
var escape = item.replace(/\//g, '-'); // fix slashes in dates
//var escape = encodeURI(escape);
var escape = encodeURIComponent(escape); // fix % and more
if (item == '') escape = 'NULL';
//document.getElementById("div_debug").innerHTML += index + "," + escape + "<br>";
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
//document.getElementById("div_debug").innerHTML += this.responseText;
}
if (this.readyState == 4 && this.status != 200) {
//document.getElementById("div_debug").innerHTML += this.responseText;
}
};
//xhttp.open("GET", "/restAll/debug", true, "_SYSTEM", "SYS");
//xhttp.open("POST", "/restAll/click/" + escape + '/' + index, true, "_SYSTEM", "abc123");
xhttp.open("POST", "/restAll/click/" + escape + '/' + index, true, "_SYSTEM", "SYS");
;xhttp.send();
}
</script -->
</head>
<body>
<table>
<BREAK>zinit+1^csp.details.1 ;<<<<<<<<<<<<<<<<<<<<<<<<<<<
IRISAPP 15e1>zwrite
%CSPsc=1
%SYSLOG=1
%request=<OBJECT REFERENCE>[4@%CSP.Request]
%response=<OBJECT REFERENCE>[1@%CSP.Response]
%session=<OBJECT REFERENCE>[2@%CSP.Session]
file=0
tDEBUG=3
tData="class,dc.data.rcc.GMbadge"
tFile1=0
tFile2=0
tWhat="class"
IRISAPP 15e1>zwrite %request
%request=4@%CSP.Request ; <OREF>
+----------------- general information ---------------
| oref value: 4
| class name: %CSP.Request
| reference count: 5
+----------------- attribute values ------------------
| AppData = $lb("",64,1,"","/csp/irisapp/","",1,"","",0,1,"","","/csp/irisapp","IRISAPP","","c:\intersystems\iris\csp\irisapp\",1,"","",1,"",900,2,2,"",3600,0,1,1,"",1,"","",1,1,0,2,2) <Set>
| AppMatch = "/csp/irisapp/" <Set>
| Application = "/csp/irisapp/" <Set>
| CSPGatewayRequest = 0 <Set>
| CharSet = "" <Set>
| Class = "csp.details" <Set>
| ContentType = "" <Set>
| (ConvertCharSet) = "UTF8"
| GatewayApplication = "" <Set>
| GatewayBuild = "" <Set>
|GatewayConnectionName = "" <Set>
| GatewayError = ""
| GatewayFunctions = "" <Set>
|GatewayInstanceName = "" <Set>
| GatewayNewId = 0
|GatewaySessionCookie = "" <Set>
|GatewaySessionIdSource = "" <Set>
| GatewayTimeout = 60
| Method = "GET"
| NoResetTimeout = 0
| PageName = "Details.csp"
|ProcessedRequestType = 1
| Protocol = "HTTP/1.1" <Set>
| RegistryMethods = 1
| RequestId = ""
| Secure = 0 <Set>
| Service = "CSP" <Set>
| URL = "/csp/irisapp/Details.csp"
| URLPrefix = ""
| UserAgent = "" <Set>
+----------------- swizzled references ---------------
| i%Content = ""
| r%Content = ""
+-----------------------------------------------------
IRISAPP 15e1>zwrite %session
%session=2@%CSP.Session ; <OREF>
+----------------- general information ---------------
| oref value: 2
| class name: %CSP.Session
| %%OID: $lb("15340","%CSP.Session")
| reference count: 5
+----------------- attribute values ------------------
| %Concurrency = 1 <Set>
| AppTimeout = 900
| Application = "/csp/irisapp/" <Set>
|ApplicationLicenses = ""
| BrowserName = "" <Get>
| BrowserPlatform = "" <Get>
| BrowserVersion = "" <Get>
| ByIdGroups = ""
| CSPSessionCookie = "15340 15340"
| CookiePath = "/csp/irisapp/"
| CreateTime = "2021-11-23 18:56:54" <Set>
| Debug = 0 <Set>
| EndSession = 0 <Set>
| ErrorPage = ""
|(EventClassContext) = ""
| GetNewId = 0
| GroupId = ""
| HttpAuthorization = ""
| KeepAlive = 1
| Key = "3"_$c(3)_"±Ð~"_$c(129)_"t¼Ø´0"_$c(0)_"òÆÆùXNX"_$c(158)_"Å="_$c(152,1)_"T*"_$c(10)_"®«®"_$c(21) <Set>
| Language = "de"
| LastModified = "2021-11-23 18:58:28" <Set>
| LicenseId = "_SYSTEM" <Set>
| LogoutCleanup = 0
| MessageNumber = 1 <Set>
| Namespace = "IRISAPP"
| NewSession = 0 <Set>
| (NoLicense) = 0
| OldTimeout = 5708603608 <Set>
| PersistentHeaders = "" <Set>
| Preserve = 0 <Set>
| ProcessId = ""
| Referrer = "" <Set>
| RunNamespace = "" <Get,Set>
| SOAPRequestCount = 0
|SecureSessionCookie = 0
| SecurityContext = $lb("_SYSTEM","%All,%DB_IRISLIB,%DB_IRISSYS","%All,%DB_IRISLIB,%DB_IRISSYS",32,-559038737) <Set>
| SessionId = 15340
| SessionScope = 2
| (StickyLogin) = $lb("/csp/irisapp/","_SYSTEM",$lb("_SYSTEM","%All,%DB_IRISLIB,%DB_IRISSYS","%All,%DB_IRISLIB,%DB_IRISSYS",32,-559038737),"",0,0,0,1) <Get,Set>
| UseSessionCookie = 2
| UserAgent = ""
| UserCookieScope = 2
| nosave = 0
+--------------- calculated references ---------------
| EventClass <Get,Set>
| Username <Get>
+-----------------------------------------------------
IRISAPP 15e1>zwrite %response
%response=1@%CSP.Response ; <OREF>
+----------------- general information ---------------
| oref value: 1
| class name: %CSP.Response
| reference count: 5
+----------------- attribute values ------------------
| AllowOutputFlush = 0
|AvoidPartitionCleanup = 0
| (CSPGatewayData) = "" <Set>
| CharSet = "utf-8"
| ContentLength = ""
| ContentType = "text/html"
| CookiePath = "/csp/irisapp/"
| Domain = ""
| GzipOutput = "" <Set>
| HTTPVersion = ""
| HeaderCharSet = ""
|Headers("CACHE-CONTROL") = "no-cache"
| Headers("DATE") = "Tue, 23 Nov 2021 19:03:29 GMT"
| Headers("EXPIRES") = "Thu, 29 Oct 1998 17:04:19 GMT"
| Headers("PRAGMA") = "no-cache"
| IgnoreRESTOutput = ""
| InProgress = 1 <Set>
| Language = "de" <Set>
| NoCharSetConvert = 0
| OutputSessionToken = 1
| Redirect = ""
| ServerSideRedirect = ""
| Status = "200 OK"
| Timeout = "" <Set>
| TraceDump = 0
| UseASPredirect = 0 <Set>
| UseHttpOnly = 1
| VaryByParam = ""
+--------------- calculated references ---------------
| Expires <Get,Set>
+-----------------------------------------------------
IRISAPP 15e1>g
<BREAK>zinit+1^csp.details.1
IRISAPP 15e1>g
<!-- tr><th colspan="3">#(tName)#</th></tr -->
<tr><th>Line</th><th>*** no file ***</th><th>Line</th><th>*** no file ***</th></tr>
</table>
</body>
</html>
El siguiente ejemplo se refiere a SQL y utilizo esta propiedad calculada que falla de vez en cuando
Property Random As %Numeric(SCALE = 2) [ Calculated, SqlComputed,
SqlComputeCode = { Set rcc=$Random(5) BREAK Set {*} = $Random(10)/rcc} ];
----------------------------------------------------------------------------
SQLquery= "select top 10 id,name,random from Sample.Person"
----------------------------------------------------------------------------
throws [SQLCODE: <-350> . . . ]
[%msg: <Unexpected error executing SqlCompute code for field 'Random': <DIVIDE>%0AmBk1+5^%sqlcq.DEMO.cls33.1>]</pre></li>
es obvio que la depuración de %0AmBk1+5^%sqlcq.DEMO.cls33.1 está fuera de nuestro alcance
Para la depuración yo utilizo el SQLshell para aprovechar mi BREAK para ver todas las variables y objetos
DEMO> DEMO>do $system.SQL.Shell()
SQL Command Line Shell
---------------------------------------------------- The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]DEMO>>select top 10 id,name,random from Sample.Person
2. select top 10 id,name,random from Sample.Person ID Name Random . try { Set rcc=$Random(5) BREAK Set i%RandomO1 = $Random(10)/rcc
^
<BREAK>%0AmBk1+5^%sqlcq.DEMO.cls33.1
DEMO 10d3>zw %SNGetQueryStats=1
SQLCODE=0
rcc=1
DEMO 10d3>zwrite $this
45@%sqlcq.DEMO.cls33 ; <OREF>
+----------------- general information ---------------
| oref value: 45
| class name: %sqlcq.DEMO.cls33
| reference count: 6
+----------------- attribute values ------------------
| %CurrentResult = "45@%sqlcq.DEMO.cls33"
| %CursorNumber = 1
| %ExtendedMetadata = $lb($lb("Sample.Person||ID","%Library.BigInt",18),$lb("Sample.Person||Name","%Library.String",10),$lb("Sample.Person||Random","%Library.Numeric",14))
| %Message = ""
| %Metadata(0) = $lb(3,"ID",-5,19,"0",1,"ID","Person","Sample",0,$c(0,0,0,1,0,0,0,0,0,0,0,0,0),"Name",12,50,"0",0,"Name","Person","Sample",0,$c(0,0,0,0,0,0,0,0,0,0,0,0,0),"Random",2,15,"2",1,"Random","Person","Sample",0,$c(0,0,0,0,0,0,0,0,0,0,0,0,0))
| (%NextColumn) = 1
| %Objects = ""
| %OutputColumnCount = 0
| %Parameters = ""
| %ROWCOUNT = 0
| %ROWID = ""
| %ResultColumnCount = 3
| %SQLCODE = 0
| (%SelectMode) = 0
| %StatementType = 1
| (%delock11) = ""
| %routine = ""
| CursorState = 1
| (ID1) = 1
| (IDO1) = ""
| (NameO1) = "Newton,Olga Z."
| (PpCallArgs1) = 10
| (Vrowcnt17) = 0
| (isolationMode) = 0
| (lockstat11) = 0
| (node2val21) = $lb("",82732383,"OptiPlex Media Inc.",34350,"V968","P1169","OR","E8259","Z4197","","Newton,Olga Z.","U4734","P4","U8988","W9047","992-61-9877",-92713954810893571,"2018-07-16 17:44:34")
| (rowcnt) = 0
| (rowlimit) = 9223372036854775807
| (starttime) = 9966.938434
| (time) = .000001
| (vpRUNTIMEOUT3) = "Newton,Olga Z."
+----------------- swizzled references ---------------
| i%%PrivateTables = "" <Set>
| r%%PrivateTables = "" <Set>
| (i%%ProcCursor) = ""
| (r%%ProcCursor) = ""
| (i%%rsmd) = ""
| (r%%rsmd) = "50@%SQL.StatementMetadata"
+--------------- calculated references ---------------
| %StatementTypeName <Get>
| ID <Get> [ Aliases - id ]
| Name <Get> [ Aliases - name,NAME ]
| Random <Get> [ Aliases - random,RANDOM ]
+----------------------------------------------------- DEMO 10d3>g
Newton,Olga Z. 1 . try { Set rcc=$Random(5) BREAK Set i%RandomO1 = $Random(10)/rcc
^
<BREAK>%0AmBk1+5^%sqlcq.DEMO.cls33.1
DEMO 10d3>zwrite %SNGetQueryStats=1
SQLCODE=0
rcc=1
DEMO 10d3>g
Adam,Emily G. 7 . try { Set rcc=$Random(5) BREAK Set i%RandomO1 = $Random(10)/rcc
^
<BREAK>%0AmBk1+5^%sqlcq.DEMO.cls33.1
DEMO 10d3>zw %SNGetQueryStats=1
SQLCODE=0
rcc=0
DEMO 10d3>g ; now we get the error
[SQLCODE: <-350>]
[%msg: <Unexpected error executing SqlCompute code for field 'Random': <DIVIDE>%0AmBk1+5^%sqlcq.DEMO.cls33.1>]
2 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/137/0ms
execute time(s)/globals/cmds/disk: 44.5198s/37/839/0ms
cached query class: %sqlcq.DEMO.cls33
---------------------------------------------------------------------------
[SQL]DEMO>>q
DEMO>
Y, como puedes ver, estos métodos de depuración actualmente también funcionan en IRIS.
Esta es una práctica personal recomendada por Robert Cemper.
Artículo
Ricardo Paiva · 24 feb, 2023
En el [primer artículo](https://es.community.intersystems.com/post/depuraci%C3%B3n-web) hablé sobre probar y depurar aplicaciones web de Caché con herramientas externas. La segunda parte tratará sobre las herramientas de Caché.
Estas son:
* CSP Gateway y Webapp configuration
* CSP Gateway logging
* CSP Gateway tracing
* ISCLOG
* Custom logging
* Session events
* Output to device
### CSP Gateway y Webapp configuration
En primer lugar, si estás depurando y, sobre todo, desarrollando una aplicación front-end, no necesitas *caching*. Es muy útil en un sistema de producción, pero no durante el desarrollo. Para desactivar el registro de una aplicación web, hay que ir a: SMP → Menu → Manage Web Applications → y definir Serve Files Timeout con la configuración igual a 0. Luego hacer clic en "Save".
Después, hay que purgar el caché de la aplicación web. Para ello, ve a: SMP → System Administration → Configuration → CSP Gateway Management → System Status. Allí se encuentra la tabla "Cached Forms", la última fila es una línea Total, pulsa el botón borrar (con el punto) para borrar el caché de la aplicación web:

### CSP Gateway logging
Ya que estamos hablando de CSP Gateway, este es compatible con el registro de las solicitudes de entrada (documentación). En la pestaña Default Parameters, especifica el nivel de registro deseado (por ejemplo, v9a) y guarda los cambios. v9a (consulta otras opciones en la documentación) registra todas las solicitudes HTTP a http.log en el directorio principal de Gateway. Este es un ejemplo de solicitud capturada:
GET /forms/form/info/Form.Test.Person HTTP/1.1
Host: localhost:57772
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:57772/formsui/index.html
Cookie: CSPSESSIONID-SP-57772-UP-forms-=001000000000yxiocLLb8bbc9SVXQJC5WMU831n2sENf4OGeGa; CSPWSERVERID=144zwBTP
Dnt: 1
Connection: keep-alive
Cache-Control: max-age=0
<>
También hay opciones para registrar el rendimiento. Los resultados pueden escribirse en un archivo o visualizarse desde la pestaña View Event Log.
### CSP Gateway tracing
Por último, se pueden rastrean las solicitudes y respuestas en la pestaña View HTTP Trace de CSP Gateway. Activa el rastreo y las solicitudes comenzarán a ser capturadas inmediatamente. No olvides apagarlo después de realizar la depuración. Este es un ejemplo de sesión de depuración:

Nota: utiliza el rastreo si puedes identificar cuál es el problema y reproducirlo fácilmente. Utiliza el registro para recoger estadísticas, elaborar perfiles de rendimiento, etc.
Además, la mayoría de los servidores web ofrecen herramientas de registro y seguimiento del rendimiento.
### ISCLOG
CSP Gateway es útil para determinar problemas de red y hacer seguimiento del rendimiento, pero para registrar lo que ocurre dentro de Caché se necesitan otras herramientas. Una de las herramientas más versátiles es ISCLOG. [Documentación](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCSP_logging).
Es un *global* que puede almacenar información sobre el procesamiento de las solicitudes actuales. Para iniciar el registro, ejecuta:
set ^%ISCLOG = 2
Y para terminar el registro, ejecuta:
set ^%ISCLOG = 0
Este es el ejemplo de una solicitud:
^%ISCLOG=0
^%ISCLOG("Data")=24
^%ISCLOG("Data",1)=$lb(2,"CSPServer","Header from CSP Size:3744 CMD:h IdSource:3","4664","FORMS","2017-06-07 10:49:21.341","%SYS.cspServer2","","")
^%ISCLOG("Data",1,0)="^h30000 "_$c(14,0,0)_"A"
^%ISCLOG("Data",2)=$lb(2,"CSPServer","[UpdateURL] Looking up: //localhost/forms/form/info path found: //localhost/forms/ Appl= "_$c(2,1,3,4)_"@"_$c(3,4,1,2,1,9,1)_"/forms/"_$c(2,1,3,4,1,2,1,2,1,2,4,3,4,1,2,1,9,1,7,1)_":%All"_$c(8,1)_"/forms"_$c(7,1)_"FORMS"_$c(2,1,2,1,3,4,1,2,1,2,1,3,4,1,2,1,4,4,132,3,3,4,2,3,4,2,2,1,4,4,16,14,2,4,3,4,1,3,4,1,2,1,3,4,1,2,1,16,1)_"Form.REST.Main"_$c(2,4,2,4),"4664","FORMS","2017-06-07 10:49:21.342","%CSP.Request.1","124","L3DfNILTaE")
^%ISCLOG("Data",3)=$lb(2,"CSPServer","[UpdateURL] Found cls: Form.REST.Main nocharsetconvert: charset:UTF-8 convert charset:UTF8","4664","FORMS","2017-06-07 10:49:21.342","%CSP.Request.1","124","L3DfNILTaE")
^%ISCLOG("Data",4)=$lb(2,"CSPServer","[HTML] Determined request type","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer2","124","L3DfNILTaE")
^%ISCLOG("Data",4,0)=$lb("usesession",1,"i%Class","Form.REST.Main","i%Service","REST","NOLOCKITEM","","i%GatewayError","")
^%ISCLOG("Data",5)=$lb(2,"CSPSession","[%LoadData] Loading CSP session, nosave=0","4664","FORMS","2017-06-07 10:49:21.342","%CSP.Session.1","","L3DfNILTaE")
^%ISCLOG("Data",5,0)=$lb(900,,0,5567742244,$c(149)_"Ù"_$c(3)_"ó»à"_$c(127)_",½"_$c(149,10)_"\"_$c(18)_"v"_$c(128,135)_"3Vô"_$c(11)_"*"_$c(154)_"PÏG¥"_$c(140,157,145,10,131)_"*",2,"FORMS","001000000000L3DfNILTaE1cDBJNjyQdyLwKq4wCXP82ld8gic",,0,"ru","L3DfNILTaE",2,1,"/forms/",$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737),"","","","2017-06-07 10:48:51","2017-06-07 10:49:04","Basic ZGV2OjEyMw==","Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0","","",0,"/forms/","","","",4,"","","","","http://localhost:57772/formsui/index.html")
^%ISCLOG("Data",6)=$lb(2,"CSPServer","[CSPDispatch]Requested GET /forms/form/info","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",7)=$lb(2,"CSPServer","[CSPDispatch] ** Start processing request newSes=0","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",7,0)="/forms/form/info"
^%ISCLOG("Data",8)=$lb(2,"CSPServer","[CSPDispatch] Service type is REST has-soapaction=0 nosave=0","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",9)=$lb(2,"CSPServer","[CSPDispatch]About to run page: Form.REST.Main","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",9,0)=$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737)
^%ISCLOG("Data",10)=$lb(2,"CSPServer","[callPage] url=/forms/form/info ; Appl: /forms/ newsession=0","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",11)=$lb(2,"CSPServer","[callPage]Imported security context ; User: UnknownUser ; Roles: %All,%Developer","4664","FORMS","2017-06-07 10:49:21.342","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",12)=$lb(2,"CSPServer","[OutputCSPGatewayData]: chd=1;","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Response.1","","L3DfNILTaE")
^%ISCLOG("Data",13)=$lb(2,"CSPResponse","[WriteHTTPHeaderCookies] Session cookie: CSPSESSIONID-SP-57772-UP-forms-=001000000000L3DfNILTaE1cDBJNjyQdyLwKq4wCXP82ld8gic; path=/forms/; httpOnly;","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Response.1","124","L3DfNILTaE")
^%ISCLOG("Data",14)=$lb(2,"CSPServer","[callPage] Return Status","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",14,0)=1
^%ISCLOG("Data",15)=$lb(2,"CSPServer","[OutputCSPGatewayData]: chd=1;","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Response.1","","L3DfNILTaE")
^%ISCLOG("Data",16)=$lb(2,"CSPServer","[Cleanup]Page EndSession=0; needToGetALicense=-1; nosave=0; loginredirect=0; sessionContext=1","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",17)=$lb(2,"CSPSession","[Cleanup] EndSession=0 nosave=0","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","124","L3DfNILTaE")
^%ISCLOG("Data",18)=$lb(2,"CSPSession","[%SaveData] Saved: ","4664","FORMS","2017-06-07 10:49:21.431","%CSP.Session.1","","L3DfNILTaE")
^%ISCLOG("Data",18,0)=$lb(900,,0,5567742261,$c(149)_"Ù"_$c(3)_"ó»à"_$c(127)_",½"_$c(149,10)_"\"_$c(18)_"v"_$c(128,135)_"3Vô"_$c(11)_"*"_$c(154)_"PÏG¥"_$c(140,157,145,10,131)_"*",2,"FORMS","001000000000L3DfNILTaE1cDBJNjyQdyLwKq4wCXP82ld8gic",,0,"ru","L3DfNILTaE",2,1,"/forms/",$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737),"","","","2017-06-07 10:48:51","2017-06-07 10:49:21","Basic ZGV2OjEyMw==","Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0","","",0,"/forms/","","","",5,"","","","","http://localhost:57772/formsui/index.html")
^%ISCLOG("Data",19)=$lb(2,"CSPServer","[Cleanup] Restoring roles before running destructor","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","","L3DfNILTaE")
^%ISCLOG("Data",19,0)=$lb("UnknownUser","%All,%Developer","%All,%Developer",64,-559038737)
^%ISCLOG("Data",20)=$lb(2,"CSPServer","[Cleanup] End","4664","FORMS","2017-06-07 10:49:21.431","%SYS.cspServer","","L3DfNILTaE")
^%ISCLOG("Data",20,0)="<-Finish processing request->"
^%ISCLOG("Data",21)=$lb(2,"GatewayRequest","[CSPGWClientRequest] GWID: ed-pc:57772; Request: sys_get_system_metricsTimeout: 5","11112","%SYS","2017-06-07 10:49:23.141","%SYS.cspServer3","","")
^%ISCLOG("Data",22)=$lb(2,"GatewayRequest","[CSPGWClientRequest] GWID: 127.0.0.1:57772; Request: sys_get_system_metricsTimeout: 5","11112","%SYS","2017-06-07 10:49:23.141","%SYS.cspServer3","","")
^%ISCLOG("Data",23)=$lb(2,"GatewayRequest","[SendSimpleCmd:Server:Failed] WebServer: 127.0.0.1:57772; Gateway Server Request Failed","11112","%SYS","2017-06-07 10:49:23.141","%CSP.Mgr.GatewayMgrImpl.1","","")
^%ISCLOG("Data",23,0)=0
^%ISCLOG("Data",24)=$lb(2,"GatewayRequest","[GetMetrics]","11112","%SYS","2017-06-07 10:49:23.141","%CSP.Mgr.GatewayMgrImpl.1","","")
^%ISCLOG("Data",24,0)="<-End Request Client->"
Además, este es un script rápido para la salida de *global* a un archivo:
set p="c:\temp\isclog.txt"
open p:"NW"
use p zw ^%ISCLOG
close p
### Custom logging
Aunque las herramientas de registro predeterminadas son bastante buenas, tienen varios problemas:
* Son genéricas y no están familiarizadas con tu solicitud
* Las opciones más detalladas afectan al rendimiento
* No están bien estructuradas, por lo que puede resultar difícil extraer información
Por lo tanto, se pueden cubrir casos más específicos escribiendo sistemas de registro personalizados. Este es un ejemplo de una clase persistente que registra parte del objeto %request:
/// Incoming request
Class Log.Request Extends %Persistent
{
/// A string indicating HTTP method used for this request.
Property method As %String;
/// A string containing the URL up to and including the page name
/// and extension, but not including the query string.
Property url As %String(MAXLEN = "");
/// A string indicating the type of browser from which the request
/// originated, as determined from the HTTP_USER_AGENT header.
Property userAgent As %String(MAXLEN = "");
/// A string indicating the MIME Content-Type of the request.
Property contentType As %String(MAXLEN = "");
/// Character set this request was send in, if not specified in the HTTP headers
/// it defaults to the character set of the page it is being submitted to.
Property charSet As %String(MAXLEN = "");
/// A <class>%CSP.Stream</class> containing the content submitted
/// with this request.
Property content As %Stream.GlobalBinary;
/// True if the communication between the browser and the web server was using
/// the secure https protocol. False for a normal http connection.
Property secure As %Boolean;
Property cgiEnvs As array Of %String(MAXLEN = "", SQLPROJECTION = "table/column");
Property data As array Of %String(MAXLEN = "", SQLPROJECTION = "table/column");
ClassMethod add() As %Status
{
set request = ..%New()
quit request.%Save()
}
Method %OnNew() As %Status [ Private, ServerOnly = 1 ]
{
#dim %request As %CSP.Request
#dim sc As %Status = $$$OK
quit:'$isObject($g(%request)) $$$ERROR($$$GeneralError, "Not a web context")
set ..charSet = %request.CharSet
if $isObject(%request.Content) {
do ..content.CopyFromAndSave(%request.Content)
} else {
set ..content = ""
}
set ..contentType = %request.ContentType
set ..method = %request.Method
set ..secure = %request.Secure
set ..url = %request.URL
set ..userAgent = %request.UserAgent
set cgi = ""
for {
set cgi=$order(%request.CgiEnvs(cgi))
quit:cgi=""
do ..cgiEnvs.SetAt(%request.CgiEnvs(cgi), cgi)
}
// Only gets first data if more than one data with the same name is present
set data = ""
for {
set data=$order(%request.Data(data))
quit:data=""
do ..data.SetAt(%request.Get(data), data)
}
quit sc
}
}
Para añadir un nuevo registro a la tabla Log.Request, agrega una llamada a tu código:
do ##class(Log.Request).add()
Es un ejemplo muy básico, puede y debe ampliarse para registrar comentarios, variables o cualquier otra cosa que puedas necesitar. La principal ventaja de este enfoque es la posibilidad de ejecutar consultas SQL sobre los datos registrados. Para obtener más información sobre cómo crear tu propio sistema de registro, consulta [este artículo](https://es.community.intersystems.com/post/logging-usando-macros-en-intersystems-cach%C3%A9).
### Session events
La clase Event (Evento) es una clase que define las interfaces que se llaman durante la vida de un objeto [%CSP.Session](http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.Session). Para utilizarla, hay que subclasificar [%CSP.SessionEvents](http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25CSP.SessionEvents) e implementar el código del método que quieres ejecutar. A continuación, en la configuración de la aplicación CSP, hay que establecer la clase de evento en la clase que has creado.
Las siguientes devoluciones de llamada están disponibles:
OnApplicationChange
OnEndRequest
OnEndSession
OnLogin
OnLogout
OnStartRequest
OnStartSession
* OnTimeout
Por ejemplo, los custom loggings comentado anteriormente pueden invocarse desde estos métodos.
### Output to device
Una de las opciones más sencillas - un método de utilidad CSP que muestra todos los objetos como respuesta. Solo hay que añadir esto a cualquier parte de tu código:
set %response.ContentType = "html"
do ##class(%CSP.Utils).DisplayAllObjects()
return $$$OK
### Conclusión
Hay varias herramientas que se puede utilizar para depurar las aplicaciones web. Elige la más adecuada para la tarea que vayas a realizar.
Y vosotros... ¿tenéis algún consejo o truco para depurar aplicaciones web desde Caché?